

import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.Vector;

import Survey.SurveyParameters;

public class Server {

	public static final int MAX_CLIENTS = 40;

	ServerSocket server;
	ServerGui gui;
	LinkedList<Pledge> pledges = new LinkedList<Pledge>();
	Vector<SurveyParameters> surveys = null;
	ClientInfo[] clients;
	ObjectInputStream[] fromclient;
	ObjectOutputStream[] toclient;
	Socket[] clientAddress;
	int clientCount;
	
	//use this to keep track of when people are joining cause of a disconnect
	boolean started = false;
	
	static int maxround = 1;
	static int players = 0;
	static double alpha = 0;
	static double A = 0;
	static double B = 0;
	static double everyoneGiven = 0;
	static int CurrRound = 1;
//	boolean isGain;
//	int allowable = 0, choice1, order;
//	int[] choice2;

	public Server() {

		clients = new ClientInfo[MAX_CLIENTS];
		clientAddress = new Socket[MAX_CLIENTS];
		fromclient = new ObjectInputStream[MAX_CLIENTS];
		toclient = new ObjectOutputStream[MAX_CLIENTS];
		
		new Console();

		// get gamble data
//		String filename = JOptionPane.showInputDialog("Please Enter Filename");
//		if (filename.indexOf(".txt") == -1) {
//			filename += ".txt";
//		}
//		DataEditor test = new DataEditor(filename);
//		test.setVisible(true);
//		pledges = new LinkedList<Pledge>();

		setupGame();
		//saveData(0);
		
//		allowable = pledges.length;

		/*try {
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(
					"surveys.dat"));
			surveys = (Vector<SurveyParameters>) in.readObject();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}*/

		// start server and gui
		gui = new ServerGui(this);

		try {
			server = new ServerSocket(4443);
			for (clientCount = 0; clientCount <= MAX_CLIENTS; clientCount++) {
				clientAddress[clientCount] = server.accept();
				fromclient[clientCount] = new ObjectInputStream(
						clientAddress[clientCount].getInputStream());
				toclient[clientCount] = new ObjectOutputStream(
						clientAddress[clientCount].getOutputStream());

				SendStartingData ssd = new SendStartingData(
						toclient[clientCount], A, B, alpha, maxround);
				GetMessages gm = new GetMessages(fromclient[clientCount], this,
						clientCount);

				ssd.start();
				gm.start();

			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/*public void sendParameters(boolean p){} {
		isGain = p;

		for (int i = 0; i < gambles.length; i++) {
			if (gambles[i].isGain() == p) {
				allowable++;
			}
		}

		for (int i = 0; i < toclient.length && toclient[i] != null; i++) {
			if (clients[i] != null) {
				clients[i].setStatus(Gamble.STAGE2_WORKING);
				gui.updateStatus(clients, i);
				SendParameters sp = new SendParameters(toclient[i], p,
						clients[i]);
				sp.run();
			}
		}
	}
*/
	
	//by this point in time, pledges is ordered by indexes.
	public void nextRound(LinkedList<Pledge> pledges2) {

		System.err.println("Made it to 'nextRound'!");

		for (int i = 0; i < pledges2.size(); i++) {
			int index = pledges.get(i).index;
			clients[index].setStatus(Pledge.STAGE1_WORKING);
			gui.updateStatus(clients, index, clients[index].guiIndex);
			SendParameters sp = new SendParameters(toclient[index], pledges2.get(i),
						clients[index]);
			sp.run();	
		}
	}
	
	public void sendParameters(int p) {

//		choice1 = p;

		for (int i = 0; i < toclient.length && toclient[i] != null; i++) {

			if (clients[i] != null) {
				clients[i].setStatus(Pledge.STAGE1_WORKING);
				gui.updateStatus(clients, i, clients[i].oldIndex);
				SendParameters sp = new SendParameters(toclient[i], p,
						clients[i]);
				sp.run();
			}

		}
	}

	public void sendParameters() {

		for (int i = 0; i < toclient.length && toclient[i] != null; i++) {

			if (clients[i] != null) {
				clients[i].setStatus(Pledge.WAITING);
				gui.updateStatus(clients, i, clients[i].oldIndex);
				SendParameters sp = new SendParameters(toclient[i], clients[i]);
				sp.run();
			}
		}
	}
	
	public void sendParameters(double[] p) {

//		choice2 = p;

		for (int i = 0; i < toclient.length && toclient[i] != null; i++) {

			if (clients[i] != null) {
				clients[i].setStatus(Pledge.DONE);
				gui.updateStatus(clients, i, clients[i].oldIndex);
				SendParameters sp = new SendParameters(toclient[i], null,
						clients[i]);
				sp.run();
			}
		}
	}

	private Calendar now;
	private PrintWriter outputStream;

	public void saveData(LinkedList<Pledge> pledges2) {
			now = Calendar.getInstance();
			NumberFormat formatter = new DecimalFormat("#0.00");
			
			//first time saving? create output file
			if(outputStream == null) {
				// all data
				try {
					outputStream = new PrintWriter(new FileOutputStream("Charity_"
							+ now.get(Calendar.DATE) + "_"
							+ (now.get(Calendar.MONTH) + 1) + "_"
							+ now.get(Calendar.YEAR) + "_" + now.get(Calendar.HOUR)
							+ now.get(Calendar.MINUTE) + ".txt"));
				} catch (FileNotFoundException ex) {
					ex.printStackTrace();
				}
			}
			
			//currRound is minus 1'd because it has already been incremented by this time.
			outputStream.println("ROUND "+(Server.CurrRound-1)+" RESULTS");
			for(Pledge p : pledges2) {
				outputStream.println("Player "+p.ID+" pledged "+formatter.format(p.lastx)+" $E if others gave at least "+formatter.format(p.lastx / p.lastrate)+" $E.");
				outputStream.println("Player "+p.ID+" gave "+formatter.format(p.x)+" $E because others gave at least "+formatter.format(p.y)+" $E.");
				outputStream.println("Player "+p.ID+" took "+p.timeToDecide+" seconds to decide.");
			}
			outputStream.println();
			if(Server.CurrRound > Server.maxround) {
				for(Pledge p : pledges2) {
					outputStream.println("Player "+p.ID+"'s payout was "+formatter.format(p.payout)+" $US.");
				}
			}
			outputStream.flush();
	}

	public void closeClients() {
		for (int i = 0; i < toclient.length; i++) {
			if (toclient[i] != null) {
				CloseClients cc = new CloseClients(toclient[i]);
				cc.start();
			}
		}
	}
	
	//newly sent in ClientInfo and the index of where it is residing now (at the end of the list)
	public int renewClient(ClientInfo temp, int index)
	{
		String clientName = ((ClientInfo) temp).subjectNumber;
		for(int i = 0; i < clients.length; i++){
			if(clients[i] != null && clients[i].subjectNumber.equals(clientName))
			{
				clients[i].setStatus(Pledge.INSTRUCTIONS);
				
				//grab the old client info and set the new index
				clients[index] = clients[i];
				clients[index].index = index;
				clients[index].oldIndex = i;
				clients[index].pledge.index = index;
				
				clients[i] = null;
				
				//send out the saved clientinfo
				SendData sd = new SendData(toclient[index], clients[index]);
				sd.start();
				
				return i;
			}
		}
		return -1;
	}

	public static void main(String[] args) {

		new Server();

	}
	
	public static void setupGame() {
		OptionPaneMultiple op = new OptionPaneMultiple();
		try {
			alpha = new Double(op.a);
			A = new Double(op.A);
			B = new Double(op.B);
			maxround = new Integer(op.rounds);
		} catch (NumberFormatException e) {
			System.err.println("Invalid number entered.");
			System.exit(0);
		}
//		String info;
//		
//		info = JOptionPane.showInputDialog("How many rounds?");
//		try {
//			maxround = new Integer(info);
//			if (maxround < 1) System.exit(0);
//		} catch (NumberFormatException e) {
//			System.err.println("Invalid number of rounds.");
//			System.exit(0);
//		}
//		
//		info = JOptionPane.showInputDialog("What is A?");
//		try {
//			setA(new Double(info));
//			if (getA() < 0) System.exit(0);
//		} catch (NumberFormatException e) {
//			System.err.println("Invalid number for A.");
//			System.exit(0);
//		}
//		
//		info = JOptionPane.showInputDialog("What is B?");
//		try {
//			setB(new Double(info));
//			if (getB() < 0) System.exit(0);
//		} catch (NumberFormatException e) {
//			System.err.println("Invalid number for B.");
//			System.exit(0);
//		}
//		
//		info = JOptionPane.showInputDialog("What is alpha?");
//		try {
//			setAlpha(new Double(info));
//			if (getAlpha() < 0) System.exit(0);
//		} catch (NumberFormatException e) {
//			System.err.println("Invalid number for alpha.");
//			System.exit(0);
//		}
	}

	public static void setA(double a) {
		A = a;
	}

	public static double getA() {
		return A;
	}

	public static void setAlpha(double alpha) {
		Server.alpha = alpha;
	}

	public static double getAlpha() {
		return alpha;
	}

	public static void setB(double b) {
		B = b;
	}

	public static double getB() {
		return B;
	}

	public void performRound(LinkedList<Pledge> pledges2, double a2,
			double b2, double alpha2) {
		//set started to true, since now the server has performed the first round
		started = true;
		
		PledgeFunc.performRound(pledges2, a2, b2, alpha2);
		setPledges(PledgeFunc.sortbyIndex(pledges2));
		PledgeFunc.print(pledges);
		everyoneGiven = PledgeFunc.getSumOfOthers(getPledges(), -1);
		System.err.println("Server completed the round!");
		Server.CurrRound++;
		
		//increment the round count for each clientInfo on the serverside
		for(int i = 0; i < clientCount; i++){
			if(clients[i] != null) clients[i].roundNum = clients[i].roundNum + 1;
		}
		
		//save the output for this round
		saveData(pledges);
		
		//setup the next round for each player
		nextRound(pledges);
		
	}

	public void setPledges(LinkedList<Pledge> pledges) {
		this.pledges = pledges;
	}

	public LinkedList<Pledge> getPledges() {
		return pledges;
	}

}

/******************************************************************************
 *gets both the client info and the choices
 ******************************************************************************/
class GetMessages extends Thread {

	ObjectInputStream in;
	Server parent;
	int index;

	public GetMessages(ObjectInputStream in, Server server, int count) {
		this.in = in;
		parent = server;
		index = count;
	}

	public void run() {

		Object temp = null;

		try {

			// start listening
			while (null != (temp = in.readObject())) {
				
				// if new client
				if (ClientInfo.class.isInstance(temp)) {
					//if the server has already performed a round
					if(parent.started)
					{
//						for(int i = 0; i < parent.clients.length; i++){
//							if(parent.clients[i].subjectNumber.equals(clientName))
//							{
//								//transplant the clientinfo to its old spot
//								parent.toclient[i] = parent.toclient[index];
//								parent.clients[i] = parent.clients[index];
//								parent.fromclient[i] = parent.fromclient[index];
//								SendData sd = new SendData(parent.toclient[i], parent.clients[i]);
//								sd.start();
//								break;
//							}
//						}
						parent.renewClient((ClientInfo) temp, index);
					}
					else
					{
						((ClientInfo) temp).setStatus(Pledge.INSTRUCTIONS);
						//set current count to next client (0 = client 0, 1 = client 1...
						parent.clients[index] = (ClientInfo) temp;
						//set each client's index to the correct index
						parent.clients[index].index = index;
						parent.clients[index].guiIndex = index;
						SendData sd = new SendData(parent.toclient[index], index);
						sd.start();
					}
				// if status update
				} else if (Integer.class.isInstance(temp)) {
					System.err.println("Server updated Client "+ index + " to " + ((Integer) temp) +"!");
						parent.clients[index].setStatus(((Integer) temp).intValue());
				// if client is sending Pledge in
				} else if (Pledge.class.isInstance(temp)) {
					//update his status to sent
					System.err.println("Recieved Pledge "+ ((Pledge) temp).index +"!");
					parent.clients[index].setStatus(Pledge.STAGE1_DONE);
					//add it to the linked list and set it to the clientInfo's pledge
					//to store incase of a disconnect
					parent.clients[index].pledge = (Pledge) temp;
					parent.clients[index].existed = true;
					parent.pledges.add((Pledge) temp);
				}
					System.err.println("ServerGUI called to update status of line" + parent.clients[index].guiIndex + "with index" + index);
					parent.gui.updateStatus(parent.clients, index, parent.clients[index].guiIndex);
			}
		} catch (EOFException e) {
			System.err.println("ERROR!");
		} catch (ClassNotFoundException e) {
			System.err.println(index);
			e.printStackTrace();
		} catch (IOException e) {
			System.err.println(index);
			e.printStackTrace();
		}

	}

}

/******************************************************************************
 * send the starting data needed for clients to begin
 ******************************************************************************/
class SendStartingData extends Thread {

	ObjectOutputStream out;
	double A;
	double B;
	double alpha;
	int maxRounds;

	public SendStartingData(ObjectOutputStream out, double A,
			double B, double alpha, int maxRounds) {
		this.out = out;
		this.A = A;
		this.B = B;
		this.alpha = alpha;
		this.maxRounds = maxRounds;
	}

	public void run() {

		try {
			out.writeObject(new Double(A));
			out.writeObject(new Double(B));
			out.writeObject(new Double(alpha));
			out.writeObject(new Integer(maxRounds));
			out.flush();

		} catch (IOException e) {
			System.err.println(e);
		}
	}
}

/******************************************************************************
 * send various parameters
 ******************************************************************************/
class SendParameters extends Thread {

	ObjectOutputStream out;
	double[] parameters;
	Pledge pl;
	ClientInfo cinfo;

//	public SendParameters(ObjectOutputStream out, boolean p, ClientInfo cinfo) {
//		this.out = out;
//		this.cinfo = cinfo;
//
//		parameters = new int[2];
//		parameters[0] = Gamble.STAGE2_WORKING;
//		parameters[1] = p ? 1 : 0;
//	}

	public SendParameters(ObjectOutputStream out, int p, ClientInfo cinfo) {
		this.out = out;
		this.cinfo = cinfo;

		parameters = new double[2];
		parameters[0] = Pledge.SURVEY; /*Is This ever reached?*/
		parameters[1] = p;
	}

	//appears to be the starting out -- sets parameter to an array of 2
	//and then makes the client appear as WAITING
	public SendParameters(ObjectOutputStream out, ClientInfo cinfo) {
		this.out = out;
		this.cinfo = cinfo;

		parameters = new double[2];
		parameters[0] = Pledge.WAITING;
	}
	
	/** THIS IS THE ONE WE USE TO ADVANCE TO THE NEXT ROUND */
	public SendParameters(ObjectOutputStream out, Pledge p, ClientInfo cinfo) {
		this.out = out;
		this.cinfo = cinfo;

		//parameters 0 contains the int necessary update the clientinfo to the next round
		//parameters 1 will contain how much the client gave from the previous round
		//parameter 2 will contain how you have given prior
		parameters = new double[2];
		parameters[0] = Pledge.STAGE1_DONE;
		pl = p;
		System.err.println("Server setup Pledge to " +cinfo.subjectNumber+ "!");
	}

	public void run() {

		cinfo.setStatus((int) parameters[0]);
		try {
			if(pl != null) {
				System.err.println("Server wrote Pledge to " +cinfo.subjectNumber+ "!");
				out.writeObject(pl);
				out.flush();
			}
			out.writeObject(parameters);
			out.flush();

		} catch (IOException e) {
			System.err.println(e);
		}
	}
}

/******************************************************************************
 * run this function from the server to close the clients
 ******************************************************************************/
class CloseClients extends Thread {

	ObjectOutputStream out;

	public CloseClients(ObjectOutputStream out) {
		this.out = out;
	}

	public void run() {

		try {
			out.writeObject("close");
			out.flush();

		} catch (IOException e) {
			System.err.println(e);
		}
	}
}